package com.comunity_cloud.client.model.fetch_file;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Hashtable;
import java.util.Map.Entry;

import com.comunity_cloud.client.model.CommunityFile;
import com.comunity_cloud.client.model.Configuration;
import com.comunity_cloud.client.model.MainModel2;
import com.comunity_cloud.client.model.CommunityFile.Chunk;
import com.comunity_cloud.client.model.communication.ConnectionsManager;
import com.comunity_cloud.client.model.communication.PacketListener;
import com.comunity_cloud.client.model.communication.PeerConnection;
import com.comunity_cloud.client.model.communication.packet.PeerPacket;
import com.comunity_cloud.client.model.communication.packet.PeerPacketHeader.Type;
import com.comunity_cloud.client.model.upload_file.FileChunkHeader;
import com.comunity_cloud.exceptions.ChunkUnavailableException;

public class FetchFilesClient implements PacketListener<FetchFileResponse>
{

	protected long totalBytesReceived;
	private FetchFileListener listener;
	private long fileSizeBytes;

	private Hashtable<String, FileInfo> files = new Hashtable<>();


	private static class FileInfo {
		FileChannel fileChannel;
		long fileSize;
		FetchFileListener fetchFileListener;
		long written = 0;
	}


	public static interface FetchFileListener {
		void fetchProgress(int progress);
		
		void finished();

		void error();
	}

	public FetchFilesClient() {
		PeerConnection.addPacketListener(Type.FETCH_FILE_RESPONSE, this);
	}

	public void fetchFile(CommunityFile communityFile, String targetPath, FetchFileListener fetchFileListener) throws IOException, ChunkUnavailableException
	{
		this.listener = fetchFileListener;

		FetchFileHeader fetchFileHeader = new FetchFileHeader();

		fetchFileHeader.setCommunityId(communityFile.getCommunity().getId());
		fetchFileHeader.setPayloadSize(0);
		fetchFileHeader.setFileName(communityFile.getPath());
		
		PeerPacket<FetchFileHeader> peerPacket = new PeerPacket<>(fetchFileHeader,null);


		fileSizeBytes = communityFile.getLength();

		RandomAccessFile rwriteFile = null;
		try {
			File writeFile = new File(targetPath + File.separator + communityFile.getPath().replace(" ", "_"));
			rwriteFile = new RandomAccessFile(writeFile, "rw");
			rwriteFile.setLength(fileSizeBytes);

			FileChannel fChannel = rwriteFile.getChannel();

			FileInfo fileInfo = new FileInfo();
			fileInfo.fileChannel = fChannel;
			fileInfo.fileSize = fileSizeBytes;
			fileInfo.fetchFileListener = listener;
			
			files.put(communityFile.getCommunity().getId()+communityFile.getPath(), fileInfo);

			for (Entry<Long, Chunk> entry : communityFile.getChunks()){
				
				Chunk chunk = entry.getValue();
				
				if(chunk.primaryHost.getClient().getId() == MainModel2.getInstance().getClientDTO().getId() || chunk.redundantHost.getClient().getId() == MainModel2.getInstance().getClientDTO().getId()) {
					//Chunk is stored on this host
					FileInputStream in = new FileInputStream(Configuration.getInstance().hostedFilesPath+communityFile.getCommunity().getName()+File.separator+chunk.totalOffset+communityFile.getPath());
					byte[] headerBytes = new byte[FileChunkHeader.SIZE];
					in.read(headerBytes);
					try {
						byte[] bytes = new byte[10*1024*1024];
						int bytesRead = 0;
						ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
						int offset = 0;
						while( (bytesRead = in.read(bytes)) != -1) {
							byteBuffer.rewind();
							byteBuffer.limit(bytesRead);
							writeToFile(communityFile.getCommunity().getId(), chunk.totalOffset+offset, bytesRead, communityFile.getPath(), byteBuffer);
							offset+=bytesRead;
						}
					} finally {
						in.close();
					}
				} else {
				
					if(ConnectionsManager.getInstance().connectionExists(chunk.primaryHost.getClient().getId())) {
						PeerConnection peerConnection = ConnectionsManager.getInstance().getPeerConnection(chunk.primaryHost.getClient().getId());
						peerConnection.sendPacket(peerPacket);					
					} else if(ConnectionsManager.getInstance().connectionExists(chunk.redundantHost.getClient().getId())) {
						PeerConnection peerConnection = ConnectionsManager.getInstance().getPeerConnection(chunk.redundantHost.getClient().getId());
						peerConnection.sendPacket(peerPacket);					
					} else {
						throw new ChunkUnavailableException(chunk);
					}
				}
			}

			
		} finally {
			try {
				rwriteFile.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	@Override
	public void packetReceived(PeerPacket<FetchFileResponse> peerPacket) {
		FetchFileResponse header = peerPacket.getHeader();
		String fileName = header.getFileName();
		ByteBuffer byteBuffer = ByteBuffer.wrap(peerPacket.getPayload(), 0, header.getPayloadSize());
		writeToFile(header.getCommunityId(),header.getOffset(),header.getPayloadSize(), fileName, byteBuffer);
	}

	private void writeToFile(long communityId, long offset, long length, String fileName,
			ByteBuffer byteBuffer) {
		FileInfo fileInfo = files.get(communityId+ fileName);
		try {
			fileInfo.fileChannel.write(byteBuffer, offset);
			fileInfo.written += length;
			fileInfo.fetchFileListener.fetchProgress((int) (((double)fileInfo.written / fileInfo.fileSize)*100));
			if(fileInfo.fileSize == fileInfo.written) {
				fileInfo.fileChannel.force(false);
				fileInfo.fileChannel.close();
				fileInfo.fetchFileListener.finished();
				files.remove(communityId+ fileName);				
			}
		} catch (IOException e) {
			fileInfo.fetchFileListener.error();
		}
	}
}
